/*
 * Copyright (C) 2012 CyberAgent
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package cn.jarlen.photoedit.filter.base;

import java.io.InputStream;
import java.nio.FloatBuffer;
import java.util.LinkedList;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.AssetManager;
import android.graphics.PointF;
import android.opengl.GLES20;

@SuppressLint("NewApi")
public class GPUImageFilter
{
	public static final String NO_FILTER_VERTEX_SHADER = ""
			+ "attribute vec4 position;\n"
			+ "attribute vec4 inputTextureCoordinate;\n" + " \n"
			+ "varying vec2 textureCoordinate;\n" + " \n" + "void main()\n"
			+ "{\n" + "    gl_Position = position;\n"
			+ "    textureCoordinate = inputTextureCoordinate.xy;\n" + "}";
	public static final String NO_FILTER_FRAGMENT_SHADER = ""
			+ "varying highp vec2 textureCoordinate;\n"
			+ " \n"
			+ "uniform sampler2D inputImageTexture;\n"
			+ " \n"
			+ "void main()\n"
			+ "{\n"
			+ "     gl_FragColor = texture2D(inputImageTexture, textureCoordinate);\n"
			+ "}";

	private final LinkedList<Runnable> mRunOnDraw;
	private final String mVertexShader;
	private final String mFragmentShader;
	protected int mGLProgId;
	protected int mGLAttribPosition;
	protected int mGLUniformTexture;
	protected int mGLAttribTextureCoordinate;
	protected int mOutputWidth;
	protected int mOutputHeight;
	private boolean mIsInitialized;

	public GPUImageFilter()
	{
		this(NO_FILTER_VERTEX_SHADER, NO_FILTER_FRAGMENT_SHADER);
	}

	public GPUImageFilter(final String vertexShader, final String fragmentShader)
	{
		mRunOnDraw = new LinkedList<Runnable>();
		mVertexShader = vertexShader;
		mFragmentShader = fragmentShader;
	}

	public final void init()
	{
		onInit();
		mIsInitialized = true;
		onInitialized();
	}

	public void onInit()
	{
		mGLProgId = OpenGlUtils.loadProgram(mVertexShader, mFragmentShader);
		mGLAttribPosition = GLES20.glGetAttribLocation(mGLProgId, "position");
		mGLUniformTexture = GLES20.glGetUniformLocation(mGLProgId,
				"inputImageTexture");
		mGLAttribTextureCoordinate = GLES20.glGetAttribLocation(mGLProgId,
				"inputTextureCoordinate");
		mIsInitialized = true;
	}

	public void onInitialized()
	{
	}

	public final void destroy()
	{
		mIsInitialized = false;
		GLES20.glDeleteProgram(mGLProgId);
		onDestroy();
	}

	public void onDestroy()
	{
	}

	public void onOutputSizeChanged(final int width, final int height)
	{
		mOutputWidth = width;
		mOutputHeight = height;
	}

	public void onDraw(final int textureId, final FloatBuffer cubeBuffer,
			final FloatBuffer textureBuffer)
	{
		GLES20.glUseProgram(mGLProgId);
		runPendingOnDrawTasks();
		if (!mIsInitialized)
		{
			return;
		}

		cubeBuffer.position(0);
		GLES20.glVertexAttribPointer(mGLAttribPosition, 2, GLES20.GL_FLOAT,
				false, 0, cubeBuffer);
		GLES20.glEnableVertexAttribArray(mGLAttribPosition);
		textureBuffer.position(0);
		GLES20.glVertexAttribPointer(mGLAttribTextureCoordinate, 2,
				GLES20.GL_FLOAT, false, 0, textureBuffer);
		GLES20.glEnableVertexAttribArray(mGLAttribTextureCoordinate);
		if (textureId != OpenGlUtils.NO_TEXTURE)
		{
			GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
			GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
			GLES20.glUniform1i(mGLUniformTexture, 0);
		}
		onDrawArraysPre();
		GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
		GLES20.glDisableVertexAttribArray(mGLAttribPosition);
		GLES20.glDisableVertexAttribArray(mGLAttribTextureCoordinate);
		GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
	}

	protected void onDrawArraysPre()
	{
	}

	protected void runPendingOnDrawTasks()
	{
		while (!mRunOnDraw.isEmpty())
		{
			mRunOnDraw.removeFirst().run();
		}
	}

	public boolean isInitialized()
	{
		return mIsInitialized;
	}

	public int getOutputWidth()
	{
		return mOutputWidth;
	}

	public int getOutputHeight()
	{
		return mOutputHeight;
	}

	public int getProgram()
	{
		return mGLProgId;
	}

	public int getAttribPosition()
	{
		return mGLAttribPosition;
	}

	public int getAttribTextureCoordinate()
	{
		return mGLAttribTextureCoordinate;
	}

	public int getUniformTexture()
	{
		return mGLUniformTexture;
	}

	protected void setInteger(final int location, final int intValue)
	{
		runOnDraw(new Runnable()
		{
			@Override
			public void run()
			{
				GLES20.glUniform1i(location, intValue);
			}
		});
	}

	public void setFloat(final int location, final float floatValue)
	{
		runOnDraw(new Runnable()
		{
			@Override
			public void run()
			{
				GLES20.glUniform1f(location, floatValue);
			}
		});
	}

	protected void setFloatVec2(final int location, final float[] arrayValue)
	{
		runOnDraw(new Runnable()
		{
			@Override
			public void run()
			{
				GLES20.glUniform2fv(location, 1, FloatBuffer.wrap(arrayValue));
			}
		});
	}

	protected void setFloatVec3(final int location, final float[] arrayValue)
	{
		runOnDraw(new Runnable()
		{
			@Override
			public void run()
			{
				GLES20.glUniform3fv(location, 1, FloatBuffer.wrap(arrayValue));
			}
		});
	}

	protected void setFloatVec4(final int location, final float[] arrayValue)
	{
		runOnDraw(new Runnable()
		{
			@Override
			public void run()
			{
				GLES20.glUniform4fv(location, 1, FloatBuffer.wrap(arrayValue));
			}
		});
	}

	protected void setFloatArray(final int location, final float[] arrayValue)
	{
		runOnDraw(new Runnable()
		{
			@Override
			public void run()
			{
				GLES20.glUniform1fv(location, arrayValue.length,
						FloatBuffer.wrap(arrayValue));
			}
		});
	}

	protected void setPoint(final int location, final PointF point)
	{
		runOnDraw(new Runnable()
		{

			@Override
			public void run()
			{
				float[] vec2 = new float[2];
				vec2[0] = point.x;
				vec2[1] = point.y;
				GLES20.glUniform2fv(location, 1, vec2, 0);
			}
		});
	}

	protected void setUniformMatrix3f(final int location, final float[] matrix)
	{
		runOnDraw(new Runnable()
		{

			@Override
			public void run()
			{
				GLES20.glUniformMatrix3fv(location, 1, false, matrix, 0);
			}
		});
	}

	protected void setUniformMatrix4f(final int location, final float[] matrix)
	{
		runOnDraw(new Runnable()
		{

			@Override
			public void run()
			{
				GLES20.glUniformMatrix4fv(location, 1, false, matrix, 0);
			}
		});
	}

	protected void runOnDraw(final Runnable runnable)
	{
		synchronized (mRunOnDraw)
		{
			mRunOnDraw.addLast(runnable);
		}
	}

	public static String loadShader(String file, Context context)
	{
		try
		{
			AssetManager assetManager = context.getAssets();
			InputStream ims = assetManager.open(file);

			String re = convertStreamToString(ims);
			ims.close();
			return re;
		} catch (Exception e)
		{
			e.printStackTrace();
		}

		return "";
	}

	public static String convertStreamToString(InputStream is)
	{
		java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A");
		return s.hasNext() ? s.next() : "";
	}

	public void onInputImageWidthChanged(int paramInt1, int paramInt2)
	{
		// TODO Auto-generated method stub
	}
}
